/**************************************************************************
   File Name [matrixmul.cu]
   Synopsis [This file defines the main function to perform
             matrix-matrix multiplication using cuda.]
   Description[Matrix multiplication: P = M * N]
**************************************************************************/
//-------------------------------------------------------------------------
// Included C libraries
//-------------------------------------------------------------------------
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
//-------------------------------------------------------------------------
// Included CUDA libraries
//-------------------------------------------------------------------------
#include <cutil.h>
//-------------------------------------------------------------------------
// Included helper functions 
//-------------------------------------------------------------------------
#include "assist.h"
//-------------------------------------------------------------------------
// Included host and gpu matrix-matrix multiplication function prototypes
#include "matrixmul.h"

/*========================================================================*/
/*                                                                        */
/*  Synopsis  [Main function]                                             */
/*  Description[Matrix multiplication: P = M * N]                         */
/*                                                                        */
/*========================================================================*/

int main (int argc, char **argv) {
   bool if_quiet = true;
   unsigned int timer_compute=0;
   int i,j,dev_id=0;
   char *matrix_id=NULL,*input_fn=NULL,*gold_fn=NULL,*gpu_fn=NULL;
   int Mw=0, Mh=0, Nw=0, Nh=0, Pw=0, Ph=0;

   if (argc==2) {
      matrix_id=strdup(argv[1]);
   }
   else {
      if (argc==3) {
         matrix_id=strdup(argv[1]);
         dev_id=atoi(argv[2]);
      }
      else {
         fprintf(stderr,"Error: Wrong input parameter numbers.\n");
         fprintf(stderr,"Ussage:\n"
            "$> matrixmul_cu [dimension <device number>]\n"
            "Examples:\n"
            "   matrixmul_cu 128\n"
            "   matrixmul_cu 128 1\n");
         exit(1);
      }
   }

   // check for GPU device -- output dev info if found
   if (DeviceSelect(dev_id)<0) { 
      fprintf(stderr,"Error: No GPU Device Not Found\n");
      exit(1);
   }

   // output general information about CUDA Device that is selected
   //DeviceInfo(dev_id);

   Mw=Mh=Nw=Nh=Pw=Ph=atoi(matrix_id);
   input_fn=(char *) malloc(30*sizeof(char));
   gold_fn=(char *) malloc(30*sizeof(char));
   gpu_fn=(char *) malloc(30*sizeof(char));
   sprintf(input_fn,"matrix_%s.bin",matrix_id);
   sprintf(gold_fn,"matrix_%s.gold",matrix_id);
   sprintf(gpu_fn,"matrix_%s.gpu",matrix_id);
   if (Pw*Ph>15*15) {
      if_quiet=true; // If not display matrix contents
   }
   //printf("Input matrix size: %d by %d\n",Mw,Mh);
//-------------------------------------------------------------------------
// Setup host side 
//-------------------------------------------------------------------------
   //printf("Setup host side environmnet:\n");
   // allocate host memory for matrices M and N  
   //printf(" M:%d x %d\n",Mw,Mh); 
   //printf(" N:%d x %d\n",Nw,Nh); 
   unsigned int size_M=Mw*Mh;
   unsigned int mem_size_M = sizeof(float)*size_M;
   float *hostM=(float*) malloc(mem_size_M);
   unsigned int size_N=Nw*Nh;
   unsigned int mem_size_N = sizeof(float)*size_N;
   float *hostN=(float*) malloc(mem_size_N);

   // allocate memory for the result on host side
   //printf("Allocate memory for the result on host side.\n");
   unsigned int size_P = Pw*Ph;
   unsigned int mem_size_P = sizeof(float)*size_P;
   float *hostP = (float*) malloc(mem_size_P);

   // Initialize the input matrices.
   //printf("Generate input matrix data for matrix M and N.\n");
   GenMatrixFile(input_fn, Pw, Ph, if_quiet);
   unsigned int *matrix=ReadMatrixFile(input_fn,Pw,Ph,true);
 
   for (i=0; i<Mw;i++) {
      for (j=0; j<Nw;j++) {
         hostM[i*Mw+j] = hostN[i*Mw+j]=(float) matrix[i*Mw+j];
      }
   }
   //=====================================================================
   // Perform matrix-matrix multiplication on host as a reference
   //=====================================================================
   //printf("Computing matrix multiplication MxN on host;\n");
   if (Pw*Ph > 512*512) {
      //printf("It takes time since matrix is larger than 512x512.\n");
   }

   // Start timer
   CUT_SAFE_CALL(cutCreateTimer(&timer_compute));
   CUT_SAFE_CALL(cutStartTimer(timer_compute));

   float* reference = (float*) malloc(mem_size_P);
   computeGold(reference, hostM, hostN, Mh, Mw, Nw);

   // End timer
   CUT_SAFE_CALL(cutStopTimer(timer_compute));

   printf("%dx%d,CPU,%f,", Mw, Mw,
      cutGetTimerValue(timer_compute));
   CUT_SAFE_CALL(cutDeleteTimer(timer_compute));

   //printf("Host Computed Reference Matrix data checksum:%g\n",
   //   CheckSum(reference,Mw, Nw));

   //if (!if_quiet) {
   //   printf(" Host Computed Reference Matrix data contents :\n");
   //   printf(" ");
   //}
   // Convert Host Matrix to unsigned and store in separte memory
   // area. Print results if if_quiet flag is not true
   matrix = (unsigned int*)malloc(Pw*Ph*sizeof(unsigned int));
   for (i = 0; i < Ph; i++) {
      for (j = 0; j < Pw; j++) {
         matrix[i*Pw+j] = (unsigned int)reference[i*Pw+ j];
         //if (!if_quiet) printf("%u ", matrix[i*Pw+j]);
      }
      //if (!if_quiet) printf("\n ");
   }  
   //if (!if_quiet) printf("\n");

   // for some reason save the host unsigned int matrix data in a file
//   WriteMatrixFile(gold_fn, matrix, Pw, Ph, 1);

   //=====================================================================
   // Perform matrix-matrix multiplication on gpu device 
   //=====================================================================

   // Start timer
   CUT_SAFE_CALL(cutCreateTimer(&timer_compute));
   CUT_SAFE_CALL(cutStartTimer(timer_compute));

   MatrixMultiplication(hostM, hostN, hostP, Mw);

   // End timer
   CUT_SAFE_CALL(cutStopTimer(timer_compute));

   printf("GPU,%f\n",
      cutGetTimerValue(timer_compute));
   CUT_SAFE_CALL(cutDeleteTimer(timer_compute));

   //printf("GPU Computed Matrix data checksum:%g\n",
   //   CheckSum(hostP,Mw, Nw));

   //if (!if_quiet) {
   //   printf(" Matrix data contents :\n");
   //   printf(" ");
   //}
   
   // Convert Device Matrix to unsigned and store in separte memory
   // area. Print results if if_quiet flag is not true
   matrix = (unsigned int*)malloc(Pw*Ph*sizeof(unsigned int));
   for (i = 0; i < Ph; i++) {
      for (j = 0; j < Pw; j++) {
         matrix[i*Pw+j] = (unsigned int)hostP[i*Pw+ j];

         //if (!if_quiet) printf("%u ", matrix[i*Pw+j]);
      }
      //if (!if_quiet) printf("\n ");
   }  
   //if (!if_quiet) printf("\n");

//   WriteMatrixFile(gpu_fn, matrix, Pw, Ph, 1);

//   CompareMatrixFile (gpu_fn, gold_fn, Pw,Ph, 1);

   // clean up memory on host
   free(matrix); matrix = NULL;
   free(reference);
   free(hostM); free(hostN); free(hostP);
   free(input_fn); free(gold_fn); free(gpu_fn);

   return 0;
}



